#include "parser.h"
#include "stdlib.h"
#include "stdio.h"
#include "yacc.tab.h"
#include "symtable.h"
#include "structtable.h"
#include <string.h>

// file descriptor for global data
FILE *fd_global;

int tabcount;

void print_node(FILE *fd, struct ast_node *n) {


	// parse node
	struct entry *e;
	struct ast_node *child;
	struct struct_entry *se;
	int i;
	printf("parsing: %d : %d\n", n->node_type, AST_FUNCTION_DEFINITION);
	switch (n->node_type) {

		/* the game itself, parent of all nodes
		 * contains statements, just print them all one by one
		 */
		case AST_GROUP:
		case AST_GAME: 
			tabcount++;
			for (int i = 0; i < n->children.elements; i++) {
				print_node(fd, (struct ast_node *) dd_da_get(&n->children, i));
			}
			tabcount--;
			break;

		/* identifier, just print it
		 */
		case AST_IDENTIFIER:
			e = symtable_entryat(n->value);

			// internal variable - width
			if (e->token == DD_INTERNAL_WIDTH) {
				fprintf(fd, "DD_GAME_WIDTH", e->lexptr);
			}
			else
			// internal variable - height
			if (e->token == DD_INTERNAL_HEIGHT) {
				fprintf(fd, "DD_GAME_HEIGHT", e->lexptr);
			}
			// normal variable
			else {
				fprintf(fd, "%s", e->lexptr);
			}

			for (int i = 0; i < n->children.elements; i++) {

				// some variables print less of the children
				if (e->value == 1 && i+1 >= n->children.elements) break;
				child = dd_da_get(&n->children, i);
				fprintf(fd, "->");
				print_node(fd, child);
			}
			break;

		case AST_ARGUMENT:
			e = symtable_entryat(n->value);
			char *type;
			switch (e->token) {
				case DD_KEYWORD_INT: type = "int"; break;
				case DD_KEYWORD_FLOAT: type = "float"; break;
				case DD_KEYWORD_STRUCT: type = "struct"; break;
				case DD_KEYWORD_VOID:
				default:
					type = "void"; break;
			}
			fprintf(fd, "%s %s", type, e->lexptr);
			break;


		/* number
		 */
		case AST_NUMBER:
			fprintf(fd, "%d", n->value);
			break;

		case AST_STRING:
			e = symtable_entryat(n->value);
			fprintf(fd, "%s", e->lexptr);
			break;

		/* definition
		 * <type> child1;
		 */
		case AST_DEFINITION:
			child = dd_da_get(&n->children, 0);
			e = symtable_entryat(child->value);
			if (e->token == DD_KEYWORD_VOID) fprintf(fd, "void ");
			else if (e->token == DD_KEYWORD_INT) fprintf(fd, "int ");
			else if (e->token == DD_KEYWORD_FLOAT) fprintf(fd, "float ");
			else if (e->token == DD_KEYWORD_STRUCT) fprintf(fd, "	struct %s *", struct_entryat(e->value)->name);
			else fprintf(fd, "%d ", e->token);
			print_node(fd, (struct ast_node *) dd_da_get(&n->children, 0));
			fprintf(fd, ";\n");
			break;

		/* function definition
		 */
		case AST_FUNCTION_DEFINITION:
			e = symtable_entryat(n->value);
			if (e->token == DD_KEYWORD_VOID) fprintf(fd, "void ");
			else if (e->token == DD_KEYWORD_INT) fprintf(fd, "int ");
			else fprintf(fd, "void ");
			fprintf(fd, "%s () {\n", e->lexptr);
			print_node(fd, (struct ast_node *) dd_da_get(&n->children, 1));
			fprintf(fd, "};\n");
			break;

		/* function definition
		 */
		case AST_FUNCTION_CALL:
			n = dd_da_get(&n->children, 0);
			e = symtable_entryat(n->value);
			print_node(fd, n);
			fprintf(fd, "(");
			e->value = 1;
			print_node(fd, n);
			e->value = 0;
			fprintf(fd, ");\n");
			break;

		/* equality
		 * child1 = child2;
		 */
		case AST_ASSIGNMENT:
			e = symtable_entryat(n->value);
			print_node(fd, (struct ast_node *) dd_da_get(&n->children, 0));
			fprintf(fd, " = ");
			print_node(fd, (struct ast_node *) dd_da_get(&n->children, 1));
			fprintf(fd, ";\n");
			break;

		case AST_ADDITION:
			fprintf(fd, "(");
			print_node(fd, (struct ast_node *) dd_da_get(&n->children, 0));
			fprintf(fd, " + ");
			print_node(fd, (struct ast_node *) dd_da_get(&n->children, 1));
			fprintf(fd, ")");
			break;

		case AST_SUBSTRACTION:
			fprintf(fd, "(");
			print_node(fd, (struct ast_node *) dd_da_get(&n->children, 0));
			fprintf(fd, " - ");
			print_node(fd, (struct ast_node *) dd_da_get(&n->children, 1));
			fprintf(fd, ")");
			break;

		case AST_MULTIPLICATION:
			fprintf(fd, "(");
			print_node(fd, (struct ast_node *) dd_da_get(&n->children, 0));
			fprintf(fd, " * ");
			print_node(fd, (struct ast_node *) dd_da_get(&n->children, 1));
			fprintf(fd, ")");
			break;

		case AST_DIVISION:
			fprintf(fd, "(");
			print_node(fd, (struct ast_node *) dd_da_get(&n->children, 0));
			fprintf(fd, " / ");
			print_node(fd, (struct ast_node *) dd_da_get(&n->children, 1));
			fprintf(fd, ")");
			break;

		case AST_LESS_THAN:
			fprintf(fd, "(");
			print_node(fd, (struct ast_node *) dd_da_get(&n->children, 0));
			fprintf(fd, " < ");
			print_node(fd, (struct ast_node *) dd_da_get(&n->children, 1));
			fprintf(fd, ")");
			break;

		case AST_MORE_THAN:
			fprintf(fd, "(");
			print_node(fd, (struct ast_node *) dd_da_get(&n->children, 0));
			fprintf(fd, " > ");
			print_node(fd, (struct ast_node *) dd_da_get(&n->children, 1));
			fprintf(fd, ")");
			break;

		case AST_EQUAL:
			fprintf(fd, "(");
			print_node(fd, (struct ast_node *) dd_da_get(&n->children, 0));
			fprintf(fd, " == ");
			print_node(fd, (struct ast_node *) dd_da_get(&n->children, 1));
			fprintf(fd, ")");
			break;

		case AST_LESS_EQUAL:
			fprintf(fd, "(");
			print_node(fd, (struct ast_node *) dd_da_get(&n->children, 0));
			fprintf(fd, " <= ");
			print_node(fd, (struct ast_node *) dd_da_get(&n->children, 1));
			fprintf(fd, ")");
			break;

		case AST_MORE_EQUAL:
			fprintf(fd, "(");
			print_node(fd, (struct ast_node *) dd_da_get(&n->children, 0));
			fprintf(fd, " >= ");
			print_node(fd, (struct ast_node *) dd_da_get(&n->children, 1));
			fprintf(fd, ")");
			break;

		case AST_GROUP_EXPRESSIONS:
			fprintf(fd, "(");
			print_node(fd, (struct ast_node *) dd_da_get(&n->children, 0));
			fprintf(fd, ")");
			break;

		case AST_ARGUMENTS:
			for (int i = 0; i < n->children.elements; i++) {
				if (i != 0) fprintf(fd, ", ");
				print_node(fd, (struct ast_node *) dd_da_get(&n->children, i));
			}
			break;

		case AST_STRUCT:
			se = struct_entryat(n->value);

			// init struct's appearence
			fprintf(fd, "struct %s {\n", se->name);
			if (se->parent >= 0) {
				fprintf(fd, "	struct %s parent;\n", struct_entryat(se->parent)->name);
			}

			// parse struct's children
			struct ast_node *group = dd_da_get(&n->children, 0);
			for (int i = 0; i < group->children.elements; i++) {
				struct ast_node *child = dd_da_get(&group->children, i);

				// parse functions as signatures
				if (child->node_type == AST_FUNCTION_DEFINITION) {
					e = symtable_entryat(child->value);

					// is not override function
					if (e->value == 0) {
						if (e->token == DD_KEYWORD_VOID) fprintf(fd, "	void ");
						else if (e->token == DD_KEYWORD_INT) fprintf(fd, "	int ");
						else fprintf(fd, "	void ");
						fprintf(fd, "(*%s)(struct %s*", e->lexptr, se->name);
						if (child->children.elements > 0) fprintf(fd, ", ");
						print_node(fd, (struct ast_node *) dd_da_get(&child->children, 0));
						fprintf(fd, ");\n", e->lexptr, se->name);
					}
				}
				// parse everything else as normal
				else {
					print_node(fd, child);
				}
			}
			fprintf(fd, "};\n");

			// struct's functions
			for (int i = 0; i < group->children.elements; i++) {
				struct ast_node *child = dd_da_get(&group->children, i);
				if (child->node_type == AST_FUNCTION_DEFINITION) {
					e = symtable_entryat(child->value);
					if (strcmp(e->lexptr, "init") == 0) continue;
					fprintf(fd, "void %s_%s(struct %s *this) {\n", se->name, e->lexptr, se->name);
					print_node(fd, (struct ast_node *) dd_da_get(&child->children, 1));
					fprintf(fd, "}\n");
				}
			}

			// struct's initialiser
			fprintf(fd, "void %s_init(struct %s *this) {\n", se->name, se->name);

			// there is parent, so initialise that
			if (se->parent >= 0) {
				fprintf(fd, "	%s_init(&this->parent);\n", struct_entryat(se->parent)->name);
			}

			// init all structs

			// init based on ast children?
			for (int i = 0; i < group->children.elements; i++) {
				struct ast_node *child = dd_da_get(&group->children, i);

				if (child->node_type == AST_FUNCTION_DEFINITION) {
					e = symtable_entryat(child->value);

					// override
					if (e->value != 0) {
						fprintf(fd, "	this->parent.%s = %s_%s;\n", e->lexptr, se->name, e->lexptr);
					}
					else
					// not `init`
					if (strcmp(e->lexptr, "init") != 0) {
						fprintf(fd, "	this->%s = %s_%s;\n", e->lexptr, se->name, e->lexptr);
					}
					// `init` function has special rules
					else {
						struct ast_node *func_child = dd_da_get(&child->children, 1);
						print_node(fd, func_child);
					}
				} else
				if (child->node_type == AST_DEFINITION) {
					struct ast_node *child2 = dd_da_get(&child->children, 0);
					e = symtable_entryat(child2->value);
					struct struct_entry *target_struct = struct_entryat(e->value);
					if (e->token == DD_KEYWORD_STRUCT) {
						fprintf(fd, "	this->%s = %s_create();\n", e->lexptr, target_struct->name);
					}

				}
			}

			// close structs init
			fprintf(fd, "}\n");

			// struct's constructor
			fprintf(fd, "struct %s *%s_create() {\n", se->name, se->name);
			fprintf(fd, "	struct %s *temp = malloc(sizeof(struct %s));\n", se->name, se->name);
			fprintf(fd, "	%s_init(temp);\n", se->name, se->name);
			fprintf(fd, "	return temp;\n");
			fprintf(fd, "}\n");
			break;

		/* if statement */
		case AST_IF:
			fprintf(fd, "if (");
			print_node(fd, dd_da_get(&n->children, 0));
			fprintf(fd, ") {\n");
			print_node(fd, dd_da_get(&n->children, 1));
			fprintf(fd, "}\n");
			break;
	} // switch

}

// responsible for creating a file and translating ast to target language
void parse(const char *filename, struct ast_node *n) {
	tabcount = 0;
	fd_global = fopen(filename, "w");
	fprintf(fd_global, "#include <stdlib.h>\n");
	fprintf(fd_global, "#include \"world.h\"\n");
	fprintf(fd_global, "#include \"sprite.h\"\n");
	fprintf(fd_global, "#include \"vector2d.h\"\n");
	print_node(fd_global, n);
	fclose(fd_global);
}
